package com.noopsyche.optimization.arithmetic;

import com.noopsyche.optimization.arithmetic.environment.Personality;
import com.noopsyche.optimization.utils.DoubleToBinary;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;


/**
 * 遗传进化算法
 */
public class GeneticAlgorithm {

    // 种群大小
    private final int popSize = 100;
    // 二进制编码长度
    private final int chromLength = 16;
    // 变异概率
    private final double pm = 0.01;
    // 随机对象
    private final Random random = new Random();

    public GeneticAlgorithm(int number) throws Exception {
        List<Personality> a = this.initPop(8);  // 种群初始化
        a = this.calObjValue(a);  // 计算适应度
        while (number > 0) {
            number -=1;
            for (Personality personality : a) {
                personality.isSelect = false;
            }

            a = this.selection(a);  // 选择操作（物竞天择）
            a = this.code(a);  // 编码（还原个体到染色体）
            a = this.crossOver(a);  // 个体交配（交换部分染色体产生新个体）
            a = this.mutation(a);  // 个体变异
            a = this.decode(a);  // 解码
            a = this.calObjValue(a);  // 计算适应度
            System.out.printf("第%d次进化，最大适应度得分为%f2%n%n", number, this.bestPersonal(a));
        }
    }

    /**
     * 初始化种群，生成一定范围内的随机数
     * @return 返回 x, y坐标
     */
    public List<Personality> initPop(int range) {
        List<Personality> personalityList = new LinkedList<>();

        for (int i=0; i< this.popSize; i++) {
            Personality person = new Personality();
            person.x = random.nextDouble() * range;
            person.y = random.nextDouble() * range;
            if (random.nextDouble() > 0.5) {
                person.x = -person.x;
                person.y = -person.y;
            }
            personalityList.add(person);
            // System.out.printf("x: %f, y: %f%n", personalityList.get(i).x, personalityList.get(i).y);
        }
        System.out.printf("种群初始化完成---种群数：%d%n", personalityList.size());
        return personalityList;
    }

    /**
     * 计算适应度
     * @param personalityList 种群
     * @return 种群分布及得分
     */
    public List<Personality> calObjValue(List<Personality> personalityList) {
        for (Personality personality : personalityList) {
            personality.score = this.objValue(personality.x, personality.y);
        }
        System.out.printf("种群计算适应度完成---种群数：%d%n", personalityList.size());
        return personalityList;
    }

    /**
     * 选择操作
     * todo: 此方法可能有BUG，种群更新后须重新设置个体的isSelect属性为false
     * todo: 另外为以后选择更好的物竞天择算法，轮盘赌算法应剔除到单独的方法内，提高代码可扩展性
     * @param personalityList 种群
     * @return 选择后的种群
     */
    public List<Personality> selection(List<Personality> personalityList) {
        List<Personality> newPersonalityList = new LinkedList<>();
        double countScore;
        // 适应度为负的直接淘汰
        personalityList.removeIf(personality -> personality.score <= 0);
        int maxSize = personalityList.size();

        int index = 0;
        double count;
        double rand;
        // 轮盘赌算法
        do {
            countScore = 0.;
            for (Personality personality:personalityList) {
                countScore += personality.score;
            }
            count = 0.;
            rand = random.nextDouble() * countScore;
            for (Personality personality : personalityList) {
                if (personality.isSelect) {
                    continue;
                }
                count += personality.score;
                if (count >= rand) {
                    // System.out.println("Selection person!");
                    personality.isSelect = true;  // 被选择了设置为true
                    // 天择
                    newPersonalityList.add(personality);
                    personalityList.remove(personality);
                    index += 1;
                    break;
                }
            }
        } while (index < (maxSize / 2));
        System.out.printf("种群物竞天择选择完成---种群数：%d%n", newPersonalityList.size());
        return newPersonalityList;
    }

    /**
     * 编码
     * @param personalityList 种群
     * @return 编码后的种群，每个数值转化为16位二进制数
     */
    public List<Personality> code(List<Personality> personalityList) throws Exception{
        List<Personality> codePersonalityList = new LinkedList<>();
        DoubleToBinary doubleToBinary = new DoubleToBinary();
        for (Personality personality : personalityList) {
            personality.xByte = doubleToBinary.getDoubleToBinary(personality.x);
            personality.yByte = doubleToBinary.getDoubleToBinary(personality.y);
            // System.out.println(personality.xByte + '\t' + personality.yByte);
            codePersonalityList.add(personality);
        }
        System.out.printf("种群编码完成---种群数：%d%n", codePersonalityList.size());
        return codePersonalityList;
    }

    /**
     * 交配繁殖（此种群为雌雄同体），对染色体的操作，重新增加种群数为100
     * todo: 适应度更高的应该更有机会交配，此思路还没添加
     * @param personalityList 种群
     * @return 交配后的种群
     */
    public List<Personality> crossOver(List<Personality> personalityList) {
        int maxSize = personalityList.size();
        int index = 0, personA, personB;
        do {
            personA = (int) (random.nextDouble() * maxSize);
            personB = (int) (random.nextDouble() * maxSize);
            if (personA != personB) {
                Personality personality = new Personality();
                for (int i=0; i<this.chromLength; i++) {
                    if (random.nextBoolean()) {
                        personality.xByte += personalityList.get(personA).xByte.charAt(i);
                    }
                    else {
                        personality.xByte += personalityList.get(personB).xByte.charAt(i);
                    }
                    if (random.nextBoolean()) {
                        personality.yByte += personalityList.get(personA).yByte.charAt(i);
                    }
                    else {
                        personality.yByte += personalityList.get(personB).yByte.charAt(i);
                    }
                }
                index += 1;
                personalityList.add(personality);
            }
        } while (index < (this.popSize - maxSize));  // 交配至种群个体数重新达到100
        System.out.printf("种群交配繁殖完成---种群数：%d%n", personalityList.size());
        return personalityList;
    }

    /**
     * 种群个体变异
     * @param personalityList 种群
     * @return 变异后的种群
     */
    public List<Personality> mutation(List<Personality> personalityList) {
        int index;
        StringBuilder temp;
        for (Personality personality : personalityList) {
            if (random.nextDouble() < this.pm) {
                // X染色体变异
                index = random.nextInt(16);
                temp = new StringBuilder(personality.xByte);
                if (temp.charAt(index) == '1') {
                    temp.setCharAt(index, '0');
                }
                else {
                    temp.setCharAt(index, '1');
                }
                personality.xByte = temp.toString();

                // Y染色体变异
                index = random.nextInt(16);
                temp = new StringBuilder(personality.yByte);
                if (temp.charAt(index) == '1') {
                    temp.setCharAt(index, '0');
                }
                else {
                    temp.setCharAt(index, '1');
                }
                personality.yByte = temp.toString();
            }
        }
        System.out.printf("种群个体变异完成---种群数：%d%n", personalityList.size());
        return personalityList;
    }

    /**
     * 解码
     * @param personalityList 种群
     * @return 解码后的种群
     */
    public List<Personality> decode(List<Personality> personalityList) {
        int integer;
        String integral, decimal;
        for (Personality personality : personalityList) {
            // 二进制整数转十进制
            integral = personality.xByte.substring(0, 8);
            integer = Integer.valueOf(integral, 2);
            // 二进制小数转十进制
            decimal = personality.xByte.substring(8, 16);
            personality.x = DoubleToBinary.bin2DecXiao(decimal) + integer;

            // 二进制整数转十进制
            integral = personality.yByte.substring(0, 8);
            integer = Integer.valueOf(integral, 2);
            // 二进制小数转十进制
            decimal = personality.yByte.substring(8, 16);
            personality.x = DoubleToBinary.bin2DecXiao(decimal) + integer;
        }
        System.out.printf("解码完成---种群数：%d%n", personalityList.size());
        return personalityList;
    }

    public double bestPersonal(List<Personality> personalityList) {
        double bestScore = 0.;
        for (Personality personality : personalityList) {
            if (personality.score > bestScore) {
                bestScore = personality.score;
            }
        }
        return bestScore;
    }

    /**
     * 适应度函数
     * @param x 横坐标
     * @param y 纵坐标
     * @return 适应度得分
     */
    public Double objValue(Double x, Double y) {
        // Double result = (1 - x / 2 + Math.pow(x, 5) + Math.pow(y, 3)) * Math.exp(Math.pow(-x, 2) - Math.pow(y, 2));
        // Double result = Math.pow(x, 2) + Math.pow(y, 2);
        Double result = x + y;
        return result;
    }
}
